﻿/*
 * LINQ to LDAP
 * http://linqtoldap.codeplex.com/
 * 
 * Copyright Alan Hatter (C) 2010-2012
 * alan.hatter@gmail.com - http://hattercoding.blogspot.com/
 * 
 * This project is subject to licensing restrictions. Visit http://linqtoldap.codeplex.com/license for more information.
 */

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using LinqToLdap.Collections;
using LinqToLdap.Exceptions;

namespace LinqToLdap.Mapping
{
    /// <summary>
    /// Class for storing a retrieving object mappings.
    /// </summary>
    public class DirectoryMapper : IDirectoryMapper
    {
        private readonly ConcurrentDictionary<Type, IObjectMapping> _mappings = new ConcurrentDictionary<Type, IObjectMapping>();

        /// <summary>
        /// Returns all mappings tracked by this object.
        /// </summary>
        /// <returns></returns>
        public ReadOnlyDictionary<Type, IObjectMapping> GetMappings()
        {
            return new ReadOnlyDictionary<Type, IObjectMapping>(_mappings);
        }

        /// <summary>
        /// Creates or retrieves the <see cref="IObjectMapping"/> from the classMap.
        /// </summary>
        /// <param name="classMap">The mapping.</param>
        /// <exception cref="MappingException">
        /// Thrown if the mapping is invalid.
        /// </exception>
        /// <returns></returns>
        public IObjectMapping Map(IClassMap classMap)
        {
            if (classMap == null) throw new ArgumentNullException("classMap");

            classMap.Validate();
            return _mappings.GetOrAdd(classMap.Type, t => classMap.ToObjectMapping());
        }

        /// <summary>
        /// Creates or retrieves the <see cref="IObjectMapping"/> from <typeparam name="T"/>.
        /// </summary>
        /// <param name="namingContext">The optional naming context.  Used for <see cref="AutoClassMap{T}"/></param>
        /// <param name="objectClasses">The optional object classes.  Used for <see cref="AutoClassMap{T}"/></param>
        /// <param name="objectClass">The optional object class.  Used for <see cref="AutoClassMap{T}"/></param>
        /// <param name="objectCategory">The optional object category.  Used for <see cref="AutoClassMap{T}"/></param>
        /// <exception cref="MappingException">
        /// Thrown if the mapping is invalid.
        /// </exception>
        /// <returns></returns>
        public IObjectMapping Map<T>(string namingContext = null, string objectClass = null, IEnumerable<string> objectClasses = null, string objectCategory = null) where T : class
        {
            return _mappings.GetOrAdd(typeof (T), t =>
                                              {
                                                  IClassMap classMap;
                                                  if (t.HasDirectorySchema())
                                                  {
                                                      classMap = new AttributeClassMap<T>();
                                                  }
                                                  else
                                                  {
                                                      if (objectClass != null)
                                                      {
                                                          if (objectClasses != null)
                                                              throw new ArgumentException("objectClass and objectClasses cannot both have a value.");

                                                          objectClasses = new[] {objectClass};
                                                      }
                                                      classMap = new AutoClassMap<T>(namingContext, objectCategory,
                                                                                     objectClasses);
                                                  }

                                                  classMap.Validate();
                                                  IObjectMapping mapping = classMap.ToObjectMapping();

                                                  return mapping;
                                              });
        }

        /// <summary>
        /// Gets the mapping for <typeparamref name="T"/>.
        /// </summary>
        /// <typeparam name="T">The type for the mapping.</typeparam>
        /// <exception cref="MappingException">
        /// Thrown if the mapping is not found.
        /// </exception>
        /// <returns></returns>
        public IObjectMapping GetMapping<T>() where T : class
        {
            try
            {
                return Map<T>();
            }
            catch (MappingException ex)
            {
                if (ex.Message.Equals("A distinguished name must be provided for the object's naming context."))
                {
                    throw new MappingException(string.Format("Mapping not found for '{0}'", typeof(T).FullName));
                }
                throw;
            }
        }
    }
}
